package com.akaovi.shareWare.client;

import com.akaovi.shareWare.mess.*;
import com.akaovi.shareWare.utils.LRUCache;
import com.akaovi.shareWare.gui.ProcessBar;
import com.akaovi.shareWare.utils.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.swing.*;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;

/**
 * @author akaovi
 * @version 1.0
 * @description 客户端，处理发送、拉取等操作
 * @date 2022/10/13 23:22
 * @since 2022/10/13 23:22
 */
public class Client implements ClientFunc {

    private static final Logger logger = LoggerFactory.getLogger(Client.class);

    private final InetAddress selfAddr;
    private InetAddress targetAddr;

    private LRUCache<String> cache = null;

    public Client(InetAddress targetAddr) {
        this.selfAddr = Utils.getSelfAddr();
        this.targetAddr = targetAddr;
    }

    public InetAddress getSelfAddr() {
        return selfAddr;
    }

    public InetAddress getTargetAddr() {
        return targetAddr;
    }

    public void setTargetAddr(InetAddress targetAddr) {
        this.targetAddr = targetAddr;
    }

    public LRUCache<String> getCache() {
        return cache;
    }

    public void setCache(LRUCache<String> cache) {
        this.cache = cache;
    }

    @Override
    public String toString() {
        return "Client{" +
                "selfAddr=" + selfAddr +
                ", targetAddr=" + targetAddr +
                '}';
    }

    /**
     * 发送文件到服务端
     *
     * @param mess
     * @param filePath
     */
    private void sendFile(FileMess mess, String filePath) {
        Socket socket = null;
        ObjectOutputStream oos = null; // 传输文件对象
        DataOutputStream dos = null; // 传输文件
        DataInputStream ack = null; // 服务器ack
        DataInputStream input = null; // 文件输入流
        byte[] buf = new byte[8192];
        ProcessBar bar = null;

        try {
            socket = new Socket(this.targetAddr, Utils.getPort());
            logger.debug("Create new Socket Connection {} to transfer file", socket);
            oos = new ObjectOutputStream(socket.getOutputStream());
            ack = new DataInputStream(socket.getInputStream());

            oos.writeObject(mess);
            oos.flush();

            if (ack.readUTF().equals("accept")) {
                dos = new DataOutputStream(socket.getOutputStream());
                input = new DataInputStream(new FileInputStream(filePath));
                int readSize = 0;
                long tempLength = 0;  // 文件实际发送大小
                bar = new ProcessBar(100, "正在发送文件");
                while (true) {
                    readSize = input.read(buf);
                    if (readSize == -1) {
                        break;
                    }
                    dos.write(buf, 0, readSize);
                    tempLength += readSize;
                    bar.updateProgressBar(((double) tempLength / mess.getFileLength()) * 100);

                    if (!ack.readUTF().equals("OK")) {
                        logger.error("Transfer File Error >>> lost connection");
                        break;
                    }
                }
                dos.flush();
                logger.info("File {} Transfer Success", filePath);
            } else {
                logger.info("Transfer File Error >>> refuse to accept (pattern)");
            }
        } catch (Exception e) {
            logger.error("Transfer File Error >>> ", e);
        } finally {
            Utils.closeProcessBar(bar);
            Utils.closeDataOutputStream(dos);
            Utils.closeDataInputStream(input);
            Utils.closeObjectOutputStream(oos);
            Utils.closeDataInputStream(ack);
            Utils.closeSocket(socket);
        }
    }

    /**
     * 发送文本到服务端的共享池
     *
     * @param mess
     */
    private void sendText(TextMess mess) {
        Socket socket = null;
        ObjectOutputStream oos = null; // 传输文本对象
        DataInputStream ack = null; // 传输完毕
        try {
            socket = new Socket(this.targetAddr, Utils.getPort());
            logger.debug("Create new Socket Connection {} to transfer text", socket);
            ack = new DataInputStream(socket.getInputStream());
            oos = new ObjectOutputStream(socket.getOutputStream());

            oos.writeObject(mess);
            oos.flush();

            if (!ack.readUTF().equals("OK")) {
                logger.debug("Transfer Text Error: connection error");
            }
            JOptionPane.showMessageDialog(
                    null,
                    "文本提交成功",
                    "Success",
                    JOptionPane.INFORMATION_MESSAGE);
        } catch (Exception e) {
            logger.error("Transfer Text Error >>> ", e);
            JOptionPane.showMessageDialog(
                    null,
                    "文本提交失败",
                    "Error",
                    JOptionPane.WARNING_MESSAGE);
        } finally {
            Utils.closeObjectOutputStream(oos);
            Utils.closeDataInputStream(ack);
            Utils.closeSocket(socket);
        }
    }

    /**
     * 从服务端的文本共享池拉取文本
     *
     * @param mess
     * @return
     */
    private void getCache(CacheMess mess) {
        Socket socket = null;
        ObjectOutputStream oos = null;
        ObjectInputStream ois = null;

        try {
            socket = new Socket(this.targetAddr, Utils.getPort());
            logger.debug("connect to {} ", socket);
            oos = new ObjectOutputStream(socket.getOutputStream());
            oos.writeObject(mess);
            oos.flush();
            ois = new ObjectInputStream(socket.getInputStream());
            CacheMess backMess = (CacheMess) ois.readObject();
            cache = backMess.getCache();
        } catch (Exception e) {
            logger.error("getCache Error >>> ", e);
            JOptionPane.showMessageDialog(
                    null,
                    "文本更新失败",
                    "Error",
                    JOptionPane.WARNING_MESSAGE);
        } finally {
            Utils.closeObjectOutputStream(oos);
            Utils.closeObjectInputStream(ois);
            Utils.closeSocket(socket);
        }
    }

    /**
     * 分发器
     *
     * @param message
     * @param filePath
     * @return
     */
    private void send(Message message, String filePath) {
        if (message instanceof FileMess) {
            sendFile((FileMess) message, filePath);
        } else if (message instanceof TextMess) {
            sendText((TextMess) message);
        } else if (message instanceof CacheMess) {
            getCache((CacheMess) message);
        } else if (message instanceof PullFileMess) {
            getFile((PullFileMess) message);
        }
    }

    /**
     * 获取服务端文件池的文件（一个）
     *
     * @param mess
     */
    private void getFile(PullFileMess mess) {
        Socket socket = null;
        ObjectOutputStream oos = null;
        DataOutputStream output = null;
        ObjectInputStream ois = null;
        DataInputStream dis = null;
        DataOutputStream dos = null;
        ProcessBar bar = null;

        try {
            socket = new Socket(this.targetAddr, Utils.getPort());
            logger.debug("connect to {} ", socket);
            oos = new ObjectOutputStream(socket.getOutputStream());
            oos.writeObject(mess);  // 发送标识
            dis = new DataInputStream(socket.getInputStream());
            if (dis.readUTF().equals("NoFiles")) {
                JOptionPane.showMessageDialog(
                        null,
                        "文件池中没有文件",
                        "提示",
                        JOptionPane.INFORMATION_MESSAGE
                );
                return;
            }

            // 获取文件列表
            ois = new ObjectInputStream(socket.getInputStream());
            String[] fileList = (String[]) ois.readObject();

            // 以数组创建下拉框
            int idx = JOptionPane.showOptionDialog(
                    null,
                    "选择一个要下载的文件",
                    "文件下载",
                    JOptionPane.YES_NO_CANCEL_OPTION,
                    JOptionPane.INFORMATION_MESSAGE,
                    null,
                    fileList,
                    fileList[0]
            );


            if (idx == JOptionPane.CLOSED_OPTION) {
                // 不下载文件
                return;
            } else {
                // 通过下拉框返回值向服务端发送需要的文件名
                oos.writeObject(new FileMess(ContentType.FileType, null, fileList[idx]));
                oos.flush();

                FileMess fileMess = (FileMess) ois.readObject();  // 文件长度

                File file = new File(Utils.getSaveFilePath() + File.separator + fileList[idx]);
                dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)));
                bar = new ProcessBar(100, "正在接收文件");
                // 开始接收文件
                long tempLength = 0;
                byte[] buf = new byte[8192];
                // 接收服务端传过来的文件
                int readSize = 0;
                output = new DataOutputStream(socket.getOutputStream());
                while (true) {
                    readSize = dis.read(buf);
                    output.writeUTF("OK");
                    if (readSize == -1) {
                        break;
                    }
                    tempLength += readSize;
                    bar.updateProgressBar(((double) tempLength / fileMess.getFileLength()) * 100);
                    dos.write(buf, 0, readSize);
                }
                dos.flush();  // 主动刷入
                if (tempLength == fileMess.getFileLength()) {
                    JOptionPane.showMessageDialog(
                            null,
                            "接收来自" + socket.getInetAddress() + "的文件[" + fileList[idx] + "]成功",
                            "提示",
                            JOptionPane.INFORMATION_MESSAGE);
                    logger.error("File {} Accept Success", mess.getFileName());
                } else {
                    JOptionPane.showMessageDialog(
                            null,
                            "接收来自" + socket.getInetAddress() + "的文件[" + fileList[idx] + "]失败",
                            "提示",
                            JOptionPane.INFORMATION_MESSAGE);
                    logger.error("File {} Accept Error", mess.getFileName());
                    file.delete();
                }
            }
        } catch (Exception e) {
            logger.error("getFile Error --> ", e);
        } finally {
            Utils.closeProcessBar(bar);
            Utils.closeDataOutputStream(output);
            Utils.closeDataOutputStream(dos);
            Utils.closeDataInputStream(dis);
            Utils.closeObjectOutputStream(oos);
            Utils.closeObjectInputStream(ois);
            Utils.closeSocket(socket);
        }
    }

    @Override
    public void sendToShareText(TextMess textMess) {
        send(textMess, null);
    }

    @Override
    public void sendToShareFile(FileMess fileMess, String filePath) {
        send(fileMess, filePath);
    }

    @Override
    public void sendToGetFileByName() {
        send(new PullFileMess(ContentType.PullFileType, null), null);
    }

    @Override
    public String sendToGetTextShared(CacheMess mess) {
        send(mess, null);
        return cache.toString();
    }
}
